package app.agent.test

import app.agent.presentation.{ActionRenderer, CheckRenderer, StatusRenderer, SystemRenderer}
import app.agent.test.TestLocator._
import app.comet.{RunTest, StopTest, Subscriber}
import app.server.services.ServiceFactory.systemClock
import app.server.{StartedTestRun, StoppedTestRun, TestContent, UpdateTestRun, ValidatedTestRun}
import im.mange.jetboot.Css._
import im.mange.jetboot.Html._
import im.mange.jetboot._
import im.mange.jetboot.bootstrap3.Bootstrap._
import im.mange.jetboot.bootstrap3.GridSystem
import im.mange.jetboot.js.JsCmdFactory._
import im.mange.jetboot.widget.{ButtonPresentation, ServerSideButton}
import im.mange.shoreditch.engine.DateFormatForHumans
import im.mange.shoreditch.hipster.{Action, Check, Step}
import org.joda.time.{Interval, LocalDateTime}

//TODO: bug ... updates of running test still go to the screen when you select another test
//... this actually would be a useful feature in the summary e.g.
//TRxx: Step 1 running ... etc
//TODO: also would be useful to list all currently running tests, with a single line status (good for TP's too)

//TODO: slide in the content
case class TestAgent(subscriber: Subscriber) extends Renderable {
  //TODO: this is a terrible name for here ...
  private val runHolder = div("runContentAgent")
  private val nameHolder = span(id = "nameContentAgent", content = R())
  private val buttonHolder = span(id = "buttonContentAgent", content = R())
  private val detailHolder = div("detailContentAgent", nameHolder).styles(clear(both), marginBottom("10px"))
  private val staticHolder = div("staticContentAgent")
  private val contextHolder = div("contextContentAgent").styles(marginTop("10px"), clear(both)/*, fontSize(smaller)*/)
  private val statusHolder = div("statusContentAgent").styles(marginTop("15px"), clear(both))
  private val clock = systemClock()

  override def render = div(id = testAgent, detailHolder, staticHolder, runHolder, contextHolder, R(<br/>), statusHolder)/*.classes(if (Mode.isDemo) "hidden" else "")*/.render

  def hide = runHolder.hide & staticHolder.hide & contextHolder.hide & statusHolder.hide

  //TODO: sort out the hiding/emptying, it's a mess ....
  //TODO: toggle to a stop button when we are running and use 'stop'
  def onTestContent(response: TestContent) =
    runHolder.show & staticHolder.show & contextHolder.show & statusHolder.show &
//    nameHolder.fill(span(Label("T" + response.id, labelDefault), span(response.name).styles(marginLeft("5px"), fontWeight(bold)))) &
  //<h4>h4. Bootstrap heading <small>Secondary text</small></h4>
    nameHolder.fill(R(<h4>{response.testId} <small>{response.name}{buttonHolder.render}</small></h4>)) &
    buttonHolder.fill(runButton(response.testId)) &
    runHolder.fill(renderSteps(response.steps, response.testId)) &
    contextHolder.empty & statusHolder.empty

  private def runButton(testId: String) = new ServerSideButton() {
    val id = "runTest" + testId
    //TODO: this blows somewhat
    //TODO: theres a widget in here somewhere ... jetpac
    //TODO: could lose spacer and use marginLeft here
    //TODO: consider making the selected on go bold or something
    def presentation = ButtonPresentation(
      span(
        span().classes(glyphicon("play"))//.styles(marginLeft("5px"))
      )
      .classes(pullRight)
      .render
    )

    def onClick = runTest(testId)
  }

  private def stopButton(testRunId: String) = new ServerSideButton() {
    val id = "stopTest" + testRunId
    //TODO: this blows somewhat
    //TODO: theres a widget in here somewhere ... jetpac
    //TODO: could lose spacer and use marginLeft here
    //TODO: consider making the selected on go bold or something
    def presentation = ButtonPresentation(
      span(
        span().classes(glyphicon("stop"))//.styles(marginLeft("5px"))
      )
      .classes(pullRight)
      .render
    )

    def onClick = stopTest(testRunId)
  }

  private[agent] def runTest(testId: String) = {
    subscriber ! RunTest(testId)
    //this is a bit .. because I seems to get thrown away ...
//    nothing
    contextHolder.empty & statusHolder.empty & buttonHolder.empty
    //    button.element.disable
    //TODO: this should proably disable and then do an ajaxInvoke to RunTest(x) ...
    //TODO: should also clear the activityAgent - which is why this will all be easier in an agent ...
  }

  private[agent] def stopTest(testRunId: String) = {
    subscriber ! StopTest(testRunId)
    //this is a bit .. because I seems to get thrown away ...
    nothing
//    contextHolder.empty & statusHolder.empty
    //    button.element.disable
    //TODO: this should probably disable and then do an ajaxInvoke to RunTest(x) ...
    //TODO: should also clear the activityAgent - which is why this will all be easier in an agent ...
  }

  //TODO: this look more like a test run agent ...
  def onValidatedTestRun(response: ValidatedTestRun) = {
    contextHolder.fill(Composite(
      div(R(<h4>{response.testRunId} <small>{"Running with:"}</small></h4>)/*.styles(fontSize(smaller), fontWeight(bold))*/).styles(clear(both)),
      Composite(
        response.versionedServices.map(v => div(
          span(
//            span().classes(classMeUp(v.validated)).styles(marginRight("5px")),
            StatusRenderer(Some(v.offering.fold(false)(_.validated)), false),
            //TODO: ultimately don't show env here .. since that will be obvious
          v.offering match {
            case Some(o) => SystemRenderer(v.alias, Some(o.version), Some(o.env))
            case None => SystemRenderer(v.alias, Some("Not Available"), None)
          }

          ).classes(pullLeft).styles(marginTop("5px"))//,
        ).styles(clear(both))
      ):_*)))
  }

  def onStartedTestRun(response: StartedTestRun) = {
    runHolder.element.empty & runHolder.fill(Composite(
      renderSteps(response.steps, response.testId))) & statusHolder.fill(R("Starting ...")) &
      buttonHolder.fill(stopButton(response.testRunId))
  }

  def onStoppedTestRun(response: StoppedTestRun) = {
    statusHolder.fill(
      span((if (response.successful) "Passed" else "Failed" + response.reason.fold("")(" - " + _)) + duration(response.startedAt))
        //.styles(marginTop("15px"))
   ) & buttonHolder.fill(runButton(response.testId))
  }

  //TODO: really need a locator for all of these id's or store of list of them ...
  def onUpdateTestRun(response: UpdateTestRun) = {
    val attempts = response.step match {
      case Check(_,_, failedAttempts) => Some(failedAttempts + 1)
      case _ => None
    }

    Element(response.testId + "S" + response.step.id.toString).fill(renderStepDetail(response.step, response.reasons)) &
      statusHolder.fill(R("Step " + response.step.id + " running" + attempts.fold("")(" (attempt " + _ + ")") + duration(response.step.script.startedAt)))
  }

  private def duration(startedAt: Option[LocalDateTime]) = {
    val d = DateFormatForHumans.ago(new Interval(startedAt.getOrElse(systemClock().localDateTime).toDateTime, systemClock().localDateTime.toDateTime).toPeriod)
    if (d.trim.isEmpty) "" else " after " + d
  }

  //TODO: current step should be highlighted somehow
  //TODO: show the system as a lozenge or a label - <span class="label label-default">Default</span>
  private def renderSteps(steps: Seq[Step], testId: String) = //div(
    Composite(
      steps.map(s => topRow(testId, s)):_*
    )
  //)

  //TODO: whack FluidGrid and span_x
  //TODO: decide if upper or lower case alias is best rnedering
  //TODO: make a Container or something which does rows with left and right etc
  private def topRow(testId: String, s: Step) =
    div(GridSystem.row(GridSystem.col(12, renderStep(testId, s)).styles(minHeight("0px")))).classes("square-box")

  //TODO: i'm sure this should include the testRunId too
  private def renderStep(testId: String, s: Step) = {
    div(testId + "S" + s.id.toString, renderStepDetail(s, Nil))
  }

  //TODO: hmm .. its wrong we cna use a step here int he UI, because steps can be run!
  //TODO: colourise the params, soft grey or smaller maybe, snall caps, lose the @, maybe lozenege?
  //TODO: pull out ActionPresentation ... obviously will look different when rv's in place and are the current step etc and passed
  //TODO: pull out CheckPresentation ... obviously will look different when rv's in place and are the current step etc and passed
  private def renderStepDetail(s: Step, reasons: List[String]) = {
    val r = s match {
      case a:Action => ActionRenderer(a, reasons)(clock).render
      case c:Check => CheckRenderer(c, reasons)(clock).render
      case x => div(R("??? - " + x.toString)).render //throw error really ...
    }
//    r.styles(color(if (!s.completed) "gray" else "black"))
    R(r)
  }
}

case class Lozenge(id: String, content: Renderable, colour: String) extends Renderable with Styleable {
  def render = span(id = id, content).classes(classes.add("lozenge")).styles(styles.add(color(colour)/*, border("1px", solid, colour)*/)).render
}
